#pragma once

#include "Log.hpp"
#include "Sock.hpp"
#include "Util.hpp"

#define WEBROOT "wwwroot"
#define HOMEPAGE "index.html"
#define VERSION "HTTP/1.0"

static std::string CodeToDesc(int code)
{
    std::string desc;
    switch(code)
    {
        case 200:
            desc = "OK";
            break;
        case 404:
            desc = "Not Found";
            break;
        default:
            desc = "OK";
            break;
    }
    return desc;
}
static std::string SuffixToDesc(const std::string &suffix)
{
    //这里可以扩展为文件映射，将网页里的表映射进来
    if(suffix == ".html" || suffix == ".htm")
    {
        return "text/html";
    }
    else if(suffix == ".js")
    {
        return "application/x-javascript";
    }
    else if(suffix == ".css")
    {
        return "text/css";
    }
    else if(suffix == ".jpg")
    {
        return "image/jpeg";
    }
    else
    {
        return "text/html";
    }
}
class HttpRequest{
    private:
        std::string request_line;
        std::vector<std::string> request_header;
        std::string blank;
        std::string request_body;
    private:
        std::string method;
        std::string uri;
        std::string version;
        //url:path?请求参数组成
        std::string path;
        std::string query_string;//请求参数
        std::unordered_map<std::string,std::string> header_kv;
        ssize_t content_length;
        bool cgi;
        bool file_size;
        std::string suffix;
    public:
        HttpRequest()
            :blank("\n"),content_length(-1),path(WEBROOT),cgi(false),suffix("text/html")
        {}
        void SetRequestLine(const std::string &line)
        {
            request_line = line;
        }
        void RequestLineParse()
        {
            Util::StringParse(request_line,method,uri,version);
            LOG(Notice,request_line);
            LOG(Notice,method);
            LOG(Notice,uri);
            LOG(Notice,version);
        }
        void InsertHeaderLine(const std::string &line)
        {
            request_header.push_back(line);
            LOG(Notice,line);
        }
        void GetRequestHeaderParse()
        {
            for(auto it=request_header.begin();it!=request_header.end();++it)
            {
                std::string k,v;
                Util::MakeStringToKV(*it,k,v);
                LOG(Notice,k);
                LOG(Notice,v);
                if(k == "Content-length")
                {
                    content_length = Util::StringToInt(v);
                }
                header_kv.insert({k,v});
            }
        }
        std::string GetPath()
        {
            return path;
        }
        std::string GetMethod()
        {
            return method;
        }
        std::string GetQueryString()
        {
            return query_string;
        }
        std::string GetBody()
        {
            return request_body;
        }
        void SetPath(std::string _path)
        {
            path = _path;
        }
        void SetCgi()
        {
            cgi = true;
        }
        bool IsCgi()
        {
            return cgi;
        }
        void SetFileSize(ssize_t s)
        {
            file_size = s;
        }
        ssize_t GetFileSize()
        {
            return file_size;
        }
        void UriParse()
        {
            //uri存在
            std::size_t pos = uri.find('?');
            if(pos == std::string::npos)
            {
                path += uri;
            }
            else
            {
                path += uri.substr(0,pos);
                query_string = uri.substr(pos+1);
                cgi = true;
            }
        }
        void SetUriEqPath()
        {
            path += uri ;
        }
        bool IsNeedRecvBody()
        {
            //首先看请求方法是否是post,但是也可能是POst或者poSt等等
            //所以这里要进行忽略大小写的比较
            if(strcasecmp(method.c_str(),"POST") == 0 &&content_length > 0)
            {
                cgi = true;
                return true;
            }
            return false;
        }
        bool IsMethodLegal()
        {
            if(strcasecmp(method.c_str(),"POST") == 0 ||strcasecmp(method.c_str(),"GET") == 0)
            {
                return true;
            }
            return false;
        }
        bool IsGet()
        {
            bool ret = strcasecmp(method.c_str(),"GET") == 0 ? true :false;
            return ret;
        }
        bool IsPOST()
        {
            bool ret = strcasecmp(method.c_str(),"POST") == 0 ? true :false;
            return ret;
        }
        void IsAddHomePage()
        {
            if(path[path.size()-1] == '/')
            {
                path += HOMEPAGE;
            }
        }
        ssize_t GetContentLength()
        {
            return content_length;
        }
        void SetRequestBody(const std::string &body)
        {
            request_body = body;
        }
        std::string MakeSuffix()
        {
            std::size_t pos = path.rfind(".");
            if(std::string::npos != pos)
            {
                suffix = path.substr(pos);
            }
            return suffix;
        }
        ~HttpRequest()
        {}
};
class HttpResponse{
    private:
        std::string status_line;
        std::vector<std::string> response_header;
        std::string blank;
        std::string response_body;
    public:
        HttpResponse():blank("\r\n")
        {}
        void SetStatusLine(const std::string &sline)
        {
            status_line = sline;
        }
        std::string GetStatusLine()
        {
            return status_line;
        }
        const std::vector<std::string>& GetRspHeader()
        {
            return response_header;
        }
        void AddHeader(const std::string &ct)
        {
            response_header.push_back(ct);
        }

};
class EndPoint{
    private:
        int sock;
        HttpRequest req;
        HttpResponse rsp;
    private:
        void GetRequestLine()
        {
            std::string line;
            Sock::GetLine(sock,line);
            req.SetRequestLine(line);
            req.RequestLineParse();
        }
        void SetResponseStatusLine(int code)
        {
            std::string status_line;
            status_line = VERSION;
            status_line += " ";
            status_line +=std::to_string(code);
            status_line += " ";
            status_line += CodeToDesc(code);
            status_line += "\r\n";
            rsp.SetStatusLine(status_line);
        }
        void SetResponseHeaderLine()
        {
            std::string suffix = req.MakeSuffix();
            std::string content_type = "Content-Type: ";
            content_type += SuffixToDesc(suffix);
            content_type += "\r\n";
            rsp.AddHeader(content_type);

        }
        void GetRequestHeader()
        {
            std::string line;
            do{
                Sock::GetLine(sock,line);
                req.InsertHeaderLine(line);
            }while(!line.empty());
            req.GetRequestHeaderParse();
        }
        void GetRequestBody()
        {
            int len = req.GetContentLength();
            char c;
            std::string body;
            while(len)
            {
                ssize_t s = recv(sock,&c,1,0);
                body.push_back(c);
                len--;
            }
            req.SetRequestBody(body);
        }
    public:
        EndPoint(int _sock):sock(_sock){}
        void RecvRequest()
        {
            //获取完整http请求
            //分析http请求
            //读取并分析完毕第一行
            GetRequestLine();
            //读取报头 &&空行
            GetRequestHeader();
            //判断是否需要读取正文
            if(req.IsNeedRecvBody())
            {
                //
                GetRequestBody();
            }
            //已经读完了所有的请求
        }
        void MakeResponse()
        {
            //开始分析
            //只处理GET和POST方法
            int code = 200;
            ssize_t size = 0;//这里是file_size
            std::string path;
            if(!req.IsMethodLegal())
            {
                LOG(Waring,"method is not legal");
                code = 404;
                goto end;
            }
            if(req.IsGet())
            {
                req.UriParse();
            }
            else
            {
                //POST
                req.SetUriEqPath();
            }
            req.IsAddHomePage();//判断是否需要请求的是首页
            //如下三种情况：
            //get && 没有参数-》请求的资源则在path里
            //get && 有参数 -》请求的资源在path&& 参数在query_string 里
            //POST则就是uri，请求的资源在path &&参数在 body里
            path = req.GetPath();
            LOG(Notice,path);
            struct stat st;
            if(stat(path.c_str(),&st) < 0)//检测请求的资源是否存在
            {
                LOG(Waring,"html is not exist! 404");
                code = 404;
                goto end;
            }
            else
            {
                if(S_ISDIR(st.st_mode))
                {
                    //处理请求路径是文件夹的情况
                    path += "/";
                    req.SetPath(path);
                    req.IsAddHomePage();
                }
                else
                {
                    if((st.st_mode & S_IXUSR)||\
                       (st.st_mode & S_IXGRP)||\
                       (st.st_mode & S_IXOTH))
                    {
                        //处理请求路径下是可执行程序文件资源时情况
                        //这里如果传参了就采用cji处理方式，不太懂
                        req.SetCgi();
                    }
                    else
                    {
                        //处理正常的网页请求情况
                    }
                    if(!req.IsCgi())
                    {
                        req.SetFileSize(st.st_size);
                    }
                }
            }
end:
            //制作response
            SetResponseStatusLine(code);
            SetResponseHeaderLine();
            //TODO
        }
        void ExecNonCgi(const std::string &path)
        {
            ssize_t size = req.GetFileSize();
            int fd = open(path.c_str(),O_RDONLY);
            sendfile(sock,fd,nullptr,size);//学习该函数，直接在内核态两个文件交互
            close(fd);
        }
        void ExecCgi()
        {
            //std::string arg;
            //if(req.IsGet())
            //{
            //    arg = req.GetQueryString();
            //}
            //else
            //{
            //    arg = req.GetBody(); 
            //}
            std::string path = req.GetPath();
            std::string method = req.GetMethod();
            std::string method_env = "METHOD=";
            method_env += method;
            std::string query_string_env;
            std::string content_length_env;
            LOG(Notice,method);
            std::string query_string;
            std::string body;


            int pipe_in[2] = {0};
            int pipe_out[2] = {0};
            //站在被调用进程的角度
            pipe(pipe_in);
            pipe(pipe_out);

            putenv((char*)method_env.c_str());
            pid_t id = fork();
            if(id == 0)
            {
                //child
                close(pipe_in[1]);
                close(pipe_out[0]);

                dup2(pipe_in[0],0);
                dup2(pipe_out[1],1);
                //exec
                //一种方法：通过环境变量给子进程 -> GET -> query_string
                if(req.IsGet())
                {
                    query_string = req.GetQueryString();
                    query_string_env = "QUERY_STRING=";
                    query_string_env += query_string;
                    putenv((char*)query_string_env.c_str());
                }
                else if(req.IsPOST())
                {
                    content_length_env = "CONTENT-LENGTH=";
                    content_length_env += std::to_string(req.GetContentLength());
                    LOG(Notice,content_length_env);
                    putenv((char*)content_length_env.c_str());
                }
                else
                {
                    //TODO
                }
                execl(path.c_str(),path.c_str(),nullptr);
                exit(0);
            }
            close(pipe_in[0]);
            close(pipe_out[1]);
            //father
            //第二种方法：通过管道传递给子进程 -> POST -> body
            char c = 'X';
            if(req.IsPOST())
            {
                body = req.GetBody();
                LOG(Notice,body);
                int i = 0;
                for(int i = 0;i < body.size();i++)
                {
                    write(pipe_in[1],&body[i],1);
                }
            }
            ssize_t s = 0;
            do{
                s = read(pipe_out[0],&c,1);
                if(s > 0)
                {
                    send(sock,&c,1,0);
                }
            }while(s >0);
            waitpid(id,nullptr,0);
        }
        void SendResponse()
        {
            std::string line = rsp.GetStatusLine();
            send(sock,line.c_str(),line.size(),0);
            const auto &header = rsp.GetRspHeader();
            auto it = header.begin();
            for(;it != header.end();it++)
            {
                send(sock,it->c_str(),it->size(),0);
            }
            //判断是cji模式还是非cji模式
            if(req.IsCgi())
            {
                //cji处理模式
                LOG(Notice,"use cgi model!");
                ExecCgi();
            }
            else
            {
                LOG(Notice,"use non cgi model!");
                std::string path = req.GetPath();
                ExecNonCgi(path);
            }
        }
        ~EndPoint()
        {
            if(sock>=0)
            {
                close(sock);
            }
        }

};
class Entry{
    public:
        //这是多线程处理方式的代码
        /*static void *HandlerHttp(void *arg)
        {
            int sock=*(int*)arg;
            delete (int*)arg;
#ifdef TEST 
        char request[10240];
        recv(sock,request,sizeof(request),0);
        std::cout<< request <<std::endl;
        close(sock);
#else
        EndPoint *ep = new EndPoint(sock);
        ep->RecvRequest();
        ep->MakeResponse();
        ep->SendResponse();
        delete ep;
#endif*/
        //这里是线程池处理方式代码
        static void HandlerHttp(int sock)
        {
#ifdef TEST 
        char request[10240];
        recv(sock,request,sizeof(request),0);
        //std::cout<< request <<std::endl;//守护进程
        close(sock);
#else
        EndPoint *ep = new EndPoint(sock);
        ep->RecvRequest();
        ep->MakeResponse();
        ep->SendResponse();
        delete ep;
#endif
        }
};
